﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Linq;

namespace Gmantis.Controls
{
    public enum DragDropEffect
    {
        None,
        Move,
        Copy
    }

    public delegate void DragDropEventHandler(object source, DragDropEventArgs e);

    public class DragDropManager
    {
        // Fields
        private bool _autoScroll = true;
        private Canvas _canvas;
        private int _dragThreshold = 8;
        private DragDropEffect _effect;
        private Button _keyMonitor;
        private MouseEventArgs _lastMouseEvent;
        private Popup _popup;
        private Point _ptOffset;
        private Control _saveFocus;
        private Storyboard _sbScroll;
        private int _scrollDelay = 120;
        private int _scrollEdge = 5;
        private int _scrollStep = 10;
        private UIElement _source;
        private Border _sourceMarker;
        private Border _sourceMarkerParent;
        private DragSourceDictionary _sources = new DragSourceDictionary();
        private UIElement _target;
        private Border _targetMarker;
        private Border _targetMarkerParent;
        private List<UIElement> _targets = new List<UIElement>();
        private System.Windows.Controls.ScrollViewer _userScroller;

        // Events
        public event DragDropEventHandler DragAutoScroll;
        public event DragDropEventHandler DragDrop;
        public event DragDropEventHandler DragEnter;
        public event DragDropEventHandler DragLeave;
        public event DragDropEventHandler DragOver;
        public event DragDropEventHandler DragStart;

        // Methods
        private void _canvas_KeyUpDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Shift:
                case Key.Ctrl:
                    if (this._lastMouseEvent != null)
                    {
                        this._canvas_MouseMove(this._canvas, this._lastMouseEvent);
                    }
                    break;

                case Key.Alt:
                case Key.CapsLock:
                    break;

                case Key.Escape:
                    if (this._target != null)
                    {
                        this.OnDragLeave(this._lastMouseEvent);
                    }
                    this.Finish();
                    return;

                default:
                    return;
            }
        }

        private void _canvas_LayoutUpdated(object sender, EventArgs e)
        {
            if (this._canvas.IsInVisualTree())
            {
                this.UpdateSourceMarkerPosition();
            }
        }

        private void _canvas_LostMouseCapture(object sender, MouseEventArgs e)
        {
            if (this._target != null)
            {
                this.OnDragLeave(e);
            }
            this.Finish();
        }

        private void _canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (this._target != null)
            {
                this.OnDragDrop(e);
            }
            this._canvas.ReleaseMouseCapture();
        }

        private void _canvas_MouseMove(object sender, MouseEventArgs e)
        {
            this._lastMouseEvent = e;
            UIElement element = null;
            UIElement[] roots = this.GetRoots();
            foreach (UIElement element2 in roots)
            {
                FrameworkElement elem = (FrameworkElement)element2.HitTestProjectionWorkaround(InputEventArgs.GetPosition(e, element2)).FirstOrDefault<UIElement>();
                foreach (FrameworkElement element4 in elem.GetParents(true))
                {
                    if (this._targets.Contains(element4))
                    {
                        element = element4;
                        break;
                    }
                }
            }
            if (this._target != element)
            {
                if (this._target != null)
                {
                    this.OnDragLeave(e);
                }
                this._target = element;
                if (this._target != null)
                {
                    this.OnDragEnter(e);
                }
            }
            if (this._target != null)
            {
                this.OnDragOver(e);
            }
            this.PerformAutoScroll(e, roots);
            switch (((this._target == null) ? DragDropEffect.None : this._effect))
            {
                case DragDropEffect.None:
                    this._targetMarker.Opacity = 0.25;
                    this._canvas.Cursor = null;
                    break;

                case DragDropEffect.Move:
                case DragDropEffect.Copy:
                    this._targetMarker.Opacity = 1.0;
                    this._canvas.Cursor = Cursors.Hand;
                    break;
            }
            this.UpdateDropMarkerPosition(e);
            this._targetMarkerParent.Visibility = Visibility.Visible;
        }

        private void _sbScroll_Completed(object sender, EventArgs e)
        {
            if (this._lastMouseEvent != null)
            {
                this.PerformAutoScroll(this._lastMouseEvent, this.GetRoots());
            }
        }

        private void ApplyEventParameterValues(DragDropEventArgs ea)
        {
            this._effect = ea.Effect;
            this._source = ea.DragSource;
            this._target = ea.DropTarget;
            this._userScroller = ea.ScrollViewer;
        }

        private bool ApplyOffset(MouseEventArgs e, Point delta, System.Windows.Controls.ScrollViewer scroller)
        {
            Point point2 = new Point(scroller.HorizontalOffset, scroller.VerticalOffset);
            point2.X += delta.X;
            point2.Y += delta.Y;
            point2.X = Math.Max(0.0, Math.Min(scroller.ScrollableWidth, point2.X));
            point2.Y = Math.Max(0.0, Math.Min(scroller.ScrollableHeight, point2.Y));
            if ((scroller.VerticalOffset == point2.Y) && (scroller.HorizontalOffset == point2.X))
            {
                return false;
            }
            scroller.ScrollToHorizontalOffset(point2.X);
            scroller.ScrollToVerticalOffset(point2.Y);
            DragDropEventArgs args = new DragDropEventArgs(e, this._source, this._target, this._effect, scroller);
            this.OnDragAutoScroll(args);
            return true;
        }

        public void ClearSources()
        {
            foreach (UIElement element in new List<UIElement>(this._sources.Keys))
            {
                this.RegisterDragSource(element, DragDropEffect.None, ModifierKeys.None);
            }
        }

        public void ClearTargets()
        {
            foreach (UIElement element in new List<UIElement>(this._targets))
            {
                this.RegisterDropTarget(element, false);
            }
        }

        private void CreatePopup()
        {
            if (this._canvas == null)
            {
                this._canvas = new Canvas();
                this._canvas.Width = this._canvas.Height = 30000.0;
                this._canvas.MouseMove += new MouseEventHandler(this._canvas_MouseMove);
                this._canvas.MouseLeftButtonUp += new MouseButtonEventHandler(this._canvas_MouseLeftButtonUp);
                this._canvas.LostMouseCapture += new MouseEventHandler(this._canvas_LostMouseCapture);
                this._canvas.LayoutUpdated += new EventHandler(this._canvas_LayoutUpdated);
                this._keyMonitor = new Button();
                this._keyMonitor.Opacity = 0.0;
                this._keyMonitor.IsTabStop = true;
                this._canvas.Children.Add(this._keyMonitor);
                this._sourceMarker = new Border();
                Color c = Color.FromArgb(0xe0, 110, 160, 0xd7);
                this._sourceMarker.Background = this.GetMarkerBrush(c);
                this._sourceMarker.BorderBrush = new SolidColorBrush(c);
                this._sourceMarker.BorderThickness = new Thickness(3.0);
                this._sourceMarker.CornerRadius = new CornerRadius(3.0);
                this._sourceMarkerParent = new Border();
                this._sourceMarkerParent.Child = this._sourceMarker;
                this._canvas.Children.Add(this._sourceMarkerParent);
                this._targetMarker = new Border();
                c = Color.FromArgb(0xff, 0xfe, 180, 0x56);
                this._targetMarker.Background = this.GetMarkerBrush(c);
                this._targetMarker.BorderBrush = new SolidColorBrush(c);
                this._targetMarker.BorderThickness = new Thickness(3.0);
                this._targetMarker.CornerRadius = new CornerRadius(3.0);
                this._targetMarkerParent = new Border();
                this._targetMarkerParent.Child = this._targetMarker;
                this._canvas.Children.Add(this._targetMarkerParent);
                this._popup = new Popup();
                this._popup.Child = this._canvas;
            }
        }

        public void DoDragDrop(UIElement source, MouseEventArgs e, DragDropEffect effect)
        {
            this._effect = effect;
            this._source = source;
            this.CreatePopup();
            Thickness borderThickness = this._targetMarker.BorderThickness;
            Size elementSize = this.GetElementSize(source);
            this._targetMarker.Width = (elementSize.Width + borderThickness.Left) + borderThickness.Right;
            this._targetMarker.Height = (elementSize.Height + borderThickness.Top) + borderThickness.Bottom;
            this.OnDragStart(e);
            if ((this._effect != DragDropEffect.None) && (this._source != null))
            {
                if (e != null)
                {
                    this.UpdateSourceMarkerPosition();
                    this._ptOffset = InputEventArgs.GetPosition(e, source);
                    this.UpdateDropMarkerPosition(e);
                    this._sourceMarkerParent.Visibility = Visibility.Visible;
                    this._targetMarkerParent.Visibility = Visibility.Visible;
                }
                else
                {
                    this._sourceMarkerParent.Visibility = Visibility.Collapsed;
                    this._targetMarkerParent.Visibility = Visibility.Collapsed;
                    this._ptOffset = new Point(0.0, 0.0);
                }
                this._popup.IsOpen = true;
                this._canvas.TryCaptureMouse();
                this._saveFocus = PlatformIndependent.GetKeyboardFocusedElement() as Control;
                this._keyMonitor.KeyDown += new KeyEventHandler(this._canvas_KeyUpDown);
                this._keyMonitor.KeyUp += new KeyEventHandler(this._canvas_KeyUpDown);
                this._keyMonitor.IsTabStop = true;
                this._keyMonitor.Focus();
                this._canvas_MouseMove(this._canvas, e);
            }
        }

        private void Finish()
        {
            this._popup.IsOpen = false;
            this._source = (UIElement)(this._target = null);
            this._lastMouseEvent = null;
            this._canvas.Cursor = null;
            this._keyMonitor.KeyDown -= new KeyEventHandler(this._canvas_KeyUpDown);
            this._keyMonitor.KeyUp -= new KeyEventHandler(this._canvas_KeyUpDown);
            if (this._saveFocus != null)
            {
                this._saveFocus.Focus();
            }
        }

        private Size GetElementSize(UIElement e)
        {
            Size renderSize = e.RenderSize;
            if (((renderSize.Width == 0.0) && (renderSize.Height == 0.0)) && (e is FrameworkElement))
            {
                FrameworkElement element = e as FrameworkElement;
                renderSize = new Size(element.ActualWidth, element.ActualHeight);
            }
            return renderSize;
        }

        private Brush GetMarkerBrush(Color c)
        {
            LinearGradientBrush brush = new LinearGradientBrush
            {
                StartPoint = new Point(0.0, 0.0),
                EndPoint = new Point(0.0, 1.0)
            };
            for (int i = 0; i < 3; i++)
            {
                GradientStop stop = new GradientStop
                {
                    Color = Color.FromArgb((byte)(((i * (2 - i)) * 50) + 50), c.R, c.G, c.B),
                    Offset = ((double)i) / 2.0
                };
                brush.GradientStops.Add(stop);
            }
            return brush;
        }

        private UIElement[] GetRoots()
        {
            Dictionary<UIElement, object> dictionary = new Dictionary<UIElement, object>();
            Dictionary<DependencyObject, DependencyObject> parentsCache = new Dictionary<DependencyObject, DependencyObject>();
            foreach (UIElement element in this._targets)
            {
                bool flag;
                FrameworkElement topMostParent = (FrameworkElement)GetTopMostParent(element, parentsCache);
                if (topMostParent == Application.Current.RootVisual)
                {
                    flag = true;
                }
                else if (topMostParent is Popup)
                {
                    Popup popup = (Popup)topMostParent;
                    if (popup.IsOpen)
                    {
                        flag = true;
                        topMostParent = (FrameworkElement)popup.Child;
                    }
                    else
                    {
                        flag = false;
                    }
                }
                else
                {
                    flag = false;
                }
                if (flag)
                {
                    dictionary[topMostParent] = null;
                }
            }
            UIElement[] source = dictionary.Keys.ToArray<UIElement>();
            source.OrderBy<UIElement, int>(delegate(UIElement element)
            {
                if (element != Application.Current.RootVisual)
                {
                    return 0;
                }
                return 1;
            });
            return source;
        }

        private static UIElement GetTopMostParent(UIElement element, Dictionary<DependencyObject, DependencyObject> parentsCache)
        {
            DependencyObject parent;
            DependencyObject key = element;
        Label_0002:
            if (!parentsCache.TryGetValue(key, out parent))
            {
                parent = VisualTreeHelper.GetParent(key);
                parentsCache.Add(key, parent);
            }
            if (parent is UIElement)
            {
                key = (UIElement)parent;
                goto Label_0002;
            }
            if (parent != null)
            {
                key = parent;
                goto Label_0002;
            }
            FrameworkElement element2 = key as FrameworkElement;
            if ((element2 != null) && (element2.Parent is Popup))
            {
                key = element2.Parent;
                goto Label_0002;
            }
            return (key as UIElement);
        }

        protected virtual void OnDragAutoScroll(DragDropEventArgs e)
        {
            if (this.DragAutoScroll != null)
            {
                this.DragAutoScroll(this, e);
            }
        }

        protected virtual void OnDragDrop(RoutedEventArgs e)
        {
            if (this.DragDrop != null)
            {
                DragDropEventArgs args = new DragDropEventArgs(e, this._source, this._target, this._effect, this._userScroller);
                this.DragDrop(this, args);
                this.ApplyEventParameterValues(args);
            }
        }

        protected virtual void OnDragEnter(RoutedEventArgs e)
        {
            if (this.DragEnter != null)
            {
                DragDropEventArgs args = new DragDropEventArgs(e, this._source, this._target, this._effect, this._userScroller);
                this.DragEnter(this, args);
                this.ApplyEventParameterValues(args);
            }
        }

        protected virtual void OnDragLeave(RoutedEventArgs e)
        {
            if (this.DragLeave != null)
            {
                DragDropEventArgs args = new DragDropEventArgs(e, this._source, this._target, this._effect, this._userScroller);
                this.DragLeave(this, args);
                this.ApplyEventParameterValues(args);
            }
        }

        protected virtual void OnDragOver(RoutedEventArgs e)
        {
            if (this.DragOver != null)
            {
                DragDropEventArgs args = new DragDropEventArgs(e, this._source, this._target, this._effect, this._userScroller);
                this.DragOver(this, args);
                this.ApplyEventParameterValues(args);
            }
        }

        protected virtual void OnDragStart(RoutedEventArgs e)
        {
            if (this.DragStart != null)
            {
                DragDropEventArgs args = new DragDropEventArgs(e, this._source, this._target, this._effect, this._userScroller);
                this.DragStart(this, args);
                this.ApplyEventParameterValues(args);
            }
        }

        private void OnSourceDragDelta(object sender, DragDeltaEventArgs e)
        {
            DragSourceInfo info;
            if (this._sources.TryGetValue(sender as UIElement, out info) && ((Math.Abs(e.CumulativeTranslation.X) + Math.Abs(e.CumulativeTranslation.Y)) > this.DragThreshold))
            {
                e.Complete();
                this.DoDragDrop((UIElement)sender, e.OriginalEventArgs as MouseEventArgs, info.Effect);
            }
        }

        private bool PerformAutoScroll(MouseEventArgs e, System.Windows.Controls.ScrollViewer scroller)
        {
            Point position = InputEventArgs.GetPosition(e, scroller);
            int autoScrollEdge = this.AutoScrollEdge;
            int num2 = Math.Max(0, this.AutoScrollStep);
            Point delta = new Point(0.0, 0.0)
            {
                X = (position.X < autoScrollEdge) ? ((double)-num2) : ((position.X > (scroller.ActualWidth - autoScrollEdge)) ? ((double)num2) : ((double)0)),
                Y = (position.Y < autoScrollEdge) ? ((double)-num2) : ((position.Y > (scroller.ActualHeight - autoScrollEdge)) ? ((double)num2) : ((double)0))
            };
            if (((delta.X != 0.0) || (delta.Y != 0.0)) && this.ApplyOffset(e, delta, scroller))
            {
                if (this._sbScroll == null)
                {
                    int num3 = Math.Max(0, this.AutoScrollDelay);
                    this._sbScroll = new Storyboard();
                    this._sbScroll.Duration = new Duration(TimeSpan.FromMilliseconds((double)num3));
                    this._sbScroll.Completed += new EventHandler(this._sbScroll_Completed);
                }
                this._sbScroll.Begin();
                return true;
            }
            return false;
        }

        private void PerformAutoScroll(MouseEventArgs e, UIElement[] roots)
        {
            if (this._autoScroll)
            {
                if (this._userScroller != null)
                {
                    this.PerformAutoScroll(e, this._userScroller);
                }
                else
                {
                    foreach (UIElement element in roots)
                    {
                        foreach (UIElement element2 in element.HitTestProjectionWorkaround(InputEventArgs.GetPosition(e, null)))
                        {
                            System.Windows.Controls.ScrollViewer scroller = element2 as System.Windows.Controls.ScrollViewer;
                            if (((scroller != null) && ((scroller.ScrollableHeight > 0.0) || (scroller.ScrollableWidth > 0.0))) && this.PerformAutoScroll(e, scroller))
                            {
                                break;
                            }
                        }
                    }
                }
            }
        }

        public void RegisterDragSource(UIElement source, DragDropEffect effect, ModifierKeys keys)
        {
            if (this._sources.ContainsKey(source))
            {
                DragSourceInfo info = this._sources[source];
                this._sources.Remove(source);
                info.DragHelper.FinalizeDrag();
            }
            if (effect != DragDropEffect.None)
            {
                DragSourceInfo info2 = new DragSourceInfo(keys, effect);
                this._sources[source] = info2;
                info2.DragHelper = new DragHelper(source, DragHelperMode.TranslateXY, 1.0, false, false, false, false);
                info2.DragHelper.DragDelta += new EventHandler<DragDeltaEventArgs>(this.OnSourceDragDelta);
            }
        }

        public void RegisterDropTarget(UIElement target, bool register)
        {
            int index = this._targets.IndexOf(target);
            if (register)
            {
                if (index < 0)
                {
                    this._targets.Add(target);
                }
            }
            else if (index > -1)
            {
                this._targets.RemoveAt(index);
            }
        }

        public void UnregisterDragSource(UIElement source)
        {
            this.RegisterDragSource(source, DragDropEffect.None, ModifierKeys.None);
        }

        private void UpdateDropMarkerPosition(MouseEventArgs e)
        {
            if (e != null)
            {
                Point position = InputEventArgs.GetPosition(e, ((UIElement)e.OriginalSource).GetRootVisual());
                this._targetMarkerParent.SetValue(Canvas.LeftProperty, position.X - this._ptOffset.X);
                this._targetMarkerParent.SetValue(Canvas.TopProperty, position.Y - this._ptOffset.Y);
            }
        }

        private void UpdateSourceMarkerPosition()
        {
            if (this._source != null)
            {
                Point point = this._source.TransformToVisual(this._source.GetRootVisual()).Transform(new Point());
                Thickness borderThickness = this._sourceMarker.BorderThickness;
                this._sourceMarkerParent.SetValue(Canvas.LeftProperty, point.X - borderThickness.Left);
                this._sourceMarkerParent.SetValue(Canvas.TopProperty, point.Y - borderThickness.Top);
                Size elementSize = this.GetElementSize(this._source);
                this._sourceMarker.Width = (elementSize.Width + borderThickness.Left) + borderThickness.Right;
                this._sourceMarker.Height = (elementSize.Height + borderThickness.Top) + borderThickness.Bottom;
                Rect accumulatedClip = ((FrameworkElement)this._source).GetAccumulatedClip(fe => fe is System.Windows.Controls.ScrollViewer);
                accumulatedClip.X += borderThickness.Left;
                accumulatedClip.Y += borderThickness.Top;
                RectangleGeometry geometry = new RectangleGeometry
                {
                    Rect = accumulatedClip
                };
                this._sourceMarker.Clip = geometry;
                this._sourceMarkerParent.Visibility = ((accumulatedClip.Width == 0.0) || (accumulatedClip.Height == 0.0)) ? Visibility.Collapsed : Visibility.Visible;
            }
        }

        // Properties
        [DefaultValue(true)]
        public bool AutoScroll
        {
            get { return this._autoScroll; }
            set { this._autoScroll = value; }
        }

        [DefaultValue(120)]
        public int AutoScrollDelay
        {
            get { return this._scrollDelay; }
            set
            {
                this._scrollDelay = value;
                if (this._sbScroll != null)
                {
                    this._sbScroll.Duration = new Duration(TimeSpan.FromMilliseconds((double)Math.Max(0, value)));
                }
            }
        }

        [DefaultValue(5)]
        public int AutoScrollEdge
        {
            get { return this._scrollEdge; }
            set { this._scrollEdge = value; }
        }

        [DefaultValue(10)]
        public int AutoScrollStep
        {
            get { return this._scrollStep; }
            set { this._scrollStep = value; }
        }

        public Canvas Canvas
        {
            get
            {
                this.CreatePopup();
                return this._canvas;
            }
        }

        [DefaultValue(8)]
        public int DragThreshold
        {
            get { return this._dragThreshold; }
            set { this._dragThreshold = value; }
        }

        public Border SourceMarker
        {
            get
            {
                this.CreatePopup();
                return this._sourceMarker;
            }
        }

        public Border TargetMarker
        {
            get
            {
                this.CreatePopup();
                return this._targetMarker;
            }
        }

        // Nested Types
        private class DragSourceDictionary : Dictionary<UIElement, DragDropManager.DragSourceInfo>
        {
        }

        private class DragSourceInfo
        {
            // Fields
            internal DragHelper DragHelper;
            public DragDropEffect Effect;
            public readonly ModifierKeys Keys;

            // Methods
            public DragSourceInfo(ModifierKeys keys, DragDropEffect effect)
            {
                this.Keys = keys;
                this.Effect = effect;
            }
        }
    }
}
